-- listview  
-- 减少节点创建
-- 常用实例
--[[
    local params = {
		renderClass = NewClothesRender,
		perNum = 5,
		cellEventProxy = function(eventName, params)
			
		end,
	}
    self._listview = ListViewVertical.new( self._ui["Panel/ScrollView"], params )
    
    self._listview:reloadData(data, params) 
]]

-- 参数说明：
--[[
    renderClass 渲染节点（必须实现 setCellModel 方法）
    isMultipleLayer 多层节点优化  减少drawcall 默认 nil
    paddingTop  顶部留空
    paddingBottom 底部留空
    paddingLeft 左边
    paddingRight 右边
    gapX x方向间隙 一种cell类型 会自动适配
    gapY y方向间隙 
    perNum 每行多少个

    asyncLoadNum 初始化的时候 每帧加载的个数
    firstEnterAsyncLoad 首次进入 是否分帧加载 默认true

    scrollEventListen -- 滚动监听
    allEventListen -- 所有事件监听
    cellEventProxy --cell 抛出的事件 类似onOperate
    cellTagMarkFunc -- 用于多种不同类型的 cell 区分 data里面 是属于那种cell
    foucsIndexChangeListen  --选中事件监听（需要 cell 主动抛出 详细 ListViewCellModelMetatable）
    shareDataChangeListen -- 同foucsIndexChangeListen
]]
-- 接口说明
--[[
    =====================================
    -- 加载数据(常用)
    reloadData(data, params) 
        -- data为 render 所需数据的数组
        -- params：
            startIndex 开始索引
            offsetY y方向偏移
            minDataLen 最少节点数（用于 背包那种  有很多空格子）
            forceAlign 强制对齐 格子数目 perNum * n
   

    =====================================
    --刷新格子状态 调用 cell refresh
    refreshVisibleCells()
    

    =====================================
    --遍历当前 可见区域 的cell 
    foreachVisibleCells(func)
        -- func(cell) cell 为render对象 返回true 中断循环
   

    =====================================
    --遍历当前 可见区域 的cellModel
    foreachVisibleCellModels(func)
        -- func(cellModel, data) 
            --cellModel 值 
            local cellModel = {
                pos = cc.p(tempWidth, -totalHeight), 
                tag = tag,
                size = size,
                index = index, 
                cell = nil, -- 在可见区域内 为render 否则 为nil (首次加载 异步的时候 也可能为nil)
                uiState = nil, 
                listview = self,        
            }
            setmetatable(model, {__index = ListViewCellModelMetatable})
            data = render data
    
    =====================================
    --遍历所有cellmodel 同 foreachVisibleCellModels
    foreachCellModels(func)
        -- func(cellModel, data) 

    =====================================
    --获取当前的cellmodels
    getCellModels

    =====================================
    --获取传入的 data
    getData

    =====================================
    --获取 cell 对象
    getCellByIndex(index)

    =====================================
    --获取 当前cell 在listview 里面的偏移量
    getOffsetByIndex(index)

    =====================================
    --获取 当前cell 在listview 里面的偏移量
    getOffsetByIndex(index)


    =====================================
    --跳转到指定索引
    jumpToIndex(index, extendOffset)
        -- extendOffset 额外的偏移量

    =====================================
    --滚动到指定索引
    scrollToIndex(index, endCallback, extendOffset, speed)
        -- endCallback(isEnd) 结束action的回掉 isEnd true 表示 到达指定索引
        -- extendOffset 额外的偏移量
        -- speed 速度

    
    =====================================
    --跳转到指定索引
    scrollToOffset(targetOffset, endCallback, speed)
        -- targetOffset 额外的偏移量    
]]
local ListView = class("ListView")
local Socket = require "socket"
function ListView:ctor(scrollView, params)
    self._scrollView = scrollView
    assert(params ~= nil, "please give listview params")
    self._unUsedCells = {}  -- 缓存
    self._cellModels = {} --
    --
    self._isMultipleLayer = params.isMultipleLayer
    --layout 布局相关
    self._paddingTop = params.paddingTop or 0
    self._paddingBottom = params.paddingBottom or 0
    self._paddingLeft = params.paddingLeft or 0
    self._paddingRight = params.paddingRight or 0
    self._gapX = params.gapX or 0
    self._gapY = params.gapY or 0
    --callback
    self._allEventListen = params.allEventListen
    self._scrollEventListen = params.scrollEventListen
    self._cellEventProxy = params.cellEventProxy
    self._cellTagMarkFunc = params.cellTagMarkFunc
    self._foucsIndexChangeListen = params.foucsIndexChangeListen
    self._shareDataChangeListen = params.shareDataChangeListen
    self._createCellListen = params.createCellListen
    --
    if params.renderClass then
        self._isSingleTag = true
        self._cellConfigs = {default = {renderClass = params.renderClass, perNum = params.perNum or 1}}
    else
        self._cellConfigs = params.cellConfigs
    end
    assert(self._cellConfigs ~= nil, 'self._cellConfigs can not be nil')
    local size = self._scrollView:getContentSize()

    self._listViewheight = size.height
    self._listViewWidth = size.width

    self._headIndex = 1 --预加载 headIndex
    self._tailIndex = 0
    self._realTotalNum = 0
    self._data = {}

    self._ignoreScrollEvent = false
    self._innerContainer = self._scrollView:getInnerContainer()
    self._scrollView:addEventListener(handler(self, self._onEvent))
    self:_initNode()

    -- 首次进入异步加载
    self._asyncLoadNum = params.asyncLoadNum or 4
    self._firstEnterAsyncLoad = params.firstEnterAsyncLoad ~= nil and  params.firstEnterAsyncLoad or true

    self._isFirstEnterFinish = false
    self._firstInitAction = nil
    self._asyncLoadTable = {}

    self:_init(params)
end

function ListView:_initNode()
    self._paddingNode = ccui.Widget:create()
    self._paddingNode:setPosition(self._paddingLeft, self._paddingBottom)
    self._scrollView:addChild(self._paddingNode)
   
    if self._isSingleTag and self._isMultipleLayer then
        local tagConfig = self._cellConfigs.default
        local createFunc = tagConfig.renderClass.create
        local cell =  createFunc(nil, self._isMultipleLayer)
        cell:setAnchorPoint(0, 1)
        tagConfig.cell = cell
        tagConfig.size = cell:getContentSize()
        local multipleLayerNum = cell:getChildrenCount()
        self._parentNode = custom.MultipleLayersParentNode:create(multipleLayerNum)
        self._parentNode:addChild(cell)
        self._paddingNode:addChild(self._parentNode)
        self:_recycleCell("default", cell)
    else
        self._parentNode = ccui.Widget:create()
        self._paddingNode:addChild(self._parentNode)
        for k ,v in pairs(self._cellConfigs) do
            local cell = self:_createCellByConfig(v)  
            v.cell = cell
            v.size = cell:getContentSize()
            self:_recycleCell(k, cell)
        end
    end    
end

function ListView:setVisible(b)
    self._scrollView:setVisible(b)
end

function ListView:getScrollView()
    return self._scrollView
end

--reload数据(数据条目 发生变化 布局变化)
function ListView:reloadData(data, params) 
    self:_clean()
    if self._isFirstEnterFinish then
        self:_stopAsyncLoad()
    end
    self._data = data
    params = params or {}
    self:_initCellModels(data, params)
    self:_reloadListView(params)

    -- 第一次加载的时候 分帧加载cell
    self:_startAsyncLoad()
    self._isFirstEnterFinish = true
end

function ListView:refreshData(data)
    if self._data and #self._data == #data then
        self._data = data
        self:foreachVisibleCellModels(function(cellModel, data)
            if cellModel.cell then
                cellModel.cell:setData(data)
            end
        end)
    else
        self:reloadData(data)
    end
end

function ListView:refreshVisibleCells()
    if self._tailIndex == 0 then
        return
    end
    for i = self._headIndex, self._tailIndex do
        local cellModel = self._cellModels[i]
        local cell = cellModel.cell
        if cell then
            cell:refresh()
        end
    end
end

function ListView:foreachVisibleCells(func)
    if not func then
        return
    end
    if self._tailIndex == 0 then
        return
    end
    for i = self._headIndex, self._tailIndex do
        local cellModel = self._cellModels[i]
        local cell = cellModel.cell
        if cell then
            if func(cell) then
                break
            end
        end
    end
end
--
function ListView:foreachVisibleCellModels(func)
    if not func then
        return
    end
    if self._tailIndex == 0 then
        return
    end
    for i = self._headIndex, self._tailIndex do
        local cellModel = self._cellModels[i]
        local d = self._data[i]
        if func(cellModel, d) then
            break
        end
    end
end

function ListView:foreachCellModels(func)
    if not func  then
        return
    end
    for i = 1, self._realTotalNum do
        local cellModel = self._cellModels[i]
        local d = self._data[i]
        if func(cellModel, d) then
            break
        end
    end
end

function ListView:getCellModels()
    return self._cellModels
end

function ListView:getData()
    return self._data
end

function ListView:getDataByIndex(index)
    return self._data[index]
end

function ListView:getCellModelByIndex(index)
    return self._cellModels[index]
end

--获取可见区域 cell by index
function ListView:getCellByIndex(index)
    if index >= self._headIndex and index <= self._tailIndex then
        return self._cellModels[index].cell
    end
    -- print(string.format("======> warning: getCellByIndex can not get  cell out of sight \
    -- self._headIndex = %d  self._tailIndex = %d  index = %d  %s", self._headIndex, self._tailIndex, index, debug.traceback()))
end


function ListView:isInVisibleIndexs(index)
    if not index then
        return false
    end
    if index >= self._headIndex and index <= self._tailIndex then
        return true
    end
    return false
end


function ListView:getOffsetByIndex(index, extendOffset)
end

function ListView:jumpToIndex(index, extendOffset)
end

function ListView:scrollToIndex(index, endCallback, extendOffset, speed)

end

function ListView:scrollToVisibleArea(index, endCallback, speed)

end

function ListView:scrollToOffset(targetOffsetY, endCallback, speed)
end

function ListView:stopScrollAction()

end

function ListView:getHeadIndex()
    return self._headIndex
end
-- Pure Virtual Function
function ListView:_init(params)
end

function ListView:_initCellModels(data)
end

function ListView:_reloadListView(params)

end

function ListView:_updateInnerContainerSize()
    
end
--projected:
function ListView:_checkIsFirstEnterAsyncLoadEnd()
    if self._firstEnterAsyncLoad then
        return self._isFirstEnterFinish
    end
    return true
end


function ListView:_checkNeedAddAsyncLoad()
    if self._firstEnterAsyncLoad and not self._isFirstEnterFinish  then
        return true
    end
end

function ListView:_stopAsyncLoad()
    if self._firstInitAction then
        self._scrollView:stopAction(self._firstInitAction)
        self._firstInitAction = nil 
        self._asyncLoadTable = {}
        return
    end
end

-- 分帧加载
function ListView:_startAsyncLoad()
    if self:_checkNeedAddAsyncLoad() and not self._firstInitAction then
        self._firstInitAction = self._scrollView:schedule(function()
            local count = 0
            for k, v in pairs(self._asyncLoadTable) do
                if v then
                    if k >= self._headIndex and k <= self._tailIndex then
                        local cellModel = self._cellModels[k]
                        if cellModel  then
                            local cell
                            if cellModel.cell then
                                cell = cellModel.cell
                            else
                                cell = self:_getCellByTag(cellModel.tag)
                                cellModel.cell = cell
                            end
                            if cell then
                                cell:setPosition(cellModel.pos)
                                cell:setCellModel(cellModel)
                                cell:setData(self._data[cellModel.index])
                            end
                        end
                    end
                    self._asyncLoadTable[k] = false
                    count = count + 1
                    if count >= self._asyncLoadNum then
                        break
                    end
                end
            end
            if count == 0 then
               self:_stopAsyncLoad()
            end
        end, 0)
    end
end

function ListView:_getTagByData(data)
    local tag = "default"
    if self._cellTagMarkFunc then
        tag = self._cellTagMarkFunc(data)
    end
    return tag
end

--根据配置 创建格子
function ListView:_createCellByConfig(tagConfig)
    assert(tagConfig ~= nil, "can not find cell config")
    local cell
    local createFunc = tagConfig.renderClass.create
    -- 实际 测试 clone 比从新加载一个慢 ... 改为默认 不clone
    if not tagConfig.enableClone then
        cell =  createFunc(nil, self._isMultipleLayer)
        cell:setAnchorPoint(0, 1)
    else
        local cloneCell = tagConfig.cell
        if cloneCell then
            cell =  createFunc(display.cloneWidget(cloneCell), self._isMultipleLayer)
        else
            cell =  createFunc(nil, self._isMultipleLayer)
            cell:setAnchorPoint(0, 1)
            tagConfig.cell = cell
        end
    end
    assert(cell ~= nil , "createCellByConfig fail cell is nil")
    self._parentNode:addChild(cell)
    return cell
end

--获取格子
function ListView:_getCellByTag(tag)
    local caches = self._unUsedCells[tag]
    local cell
    if not caches or #caches == 0 then
        local tagConfig = self._cellConfigs[tag]
        cell = self:_createCellByConfig(tagConfig)
    else
        local lastIndex = #caches
        cell = caches[lastIndex]
        table.remove( caches, lastIndex )
        assert(cell ~= nil, "_getCellByTag cell is nil")
        cell:setVisible(true)
    end

    if self._createCellListen then
        self._createCellListen(cell)
    end
    return cell
end

function ListView:_newCellByModel(cellModel)
    if self:_checkNeedAddAsyncLoad() or self._firstInitAction then
        self._asyncLoadTable[cellModel.index] = true
    else
        local cell
        if cellModel.cell then
            print("warning cellModel.cell is not nil", debug.traceback())
            cell = cellModel.cell
        else
            cell = self:_getCellByTag(cellModel.tag)
            cellModel.cell = cell
        end
        if cell then
            cell:setPosition(cellModel.pos)
            cell:setCellModel(cellModel)
            cell:setData(self._data[cellModel.index]) 
        end
    end
end

--回收 cell
function ListView:_recycleCellByModel(cellModel)
    local cell = cellModel.cell
    if not cell then
        return
    end
    self:_recycleCell(cellModel.tag, cell)
    cellModel.cell = nil
end

function ListView:_recycleCell(tag, cell)
    local caches = self._unUsedCells[tag]
    if not caches then
        caches = {}
        self._unUsedCells[tag] = caches
    end
    cell:setVisible(false)
    if cell.onRecycleCell then
        cell:onRecycleCell()
    end
    table.insert(caches, cell)
end


--回收 cell
function ListView:_recycleCellByModel(cellModel)
    local cell = cellModel.cell
    if not cell then
        return
    end
    -- print("===== _recycleCell", cell:getIndex())
    self:_recycleCell(cellModel.tag, cell)
    cellModel.cell = nil
end

--清理
function ListView:_clean()
    self:stopScrollAction()
    if self._tailIndex == 0 then
        return
    end
    for i = self._headIndex, self._tailIndex do
        local cellModel = self._cellModels[i]
        self:_recycleCellByModel(cellModel)
    end
    self._headIndex = 1
    self._tailIndex = 0
    self._asyncLoadTable = {}
end

function ListView:onCellEvent(eventName, params)
    if self._cellEventProxy then
        self._cellEventProxy(eventName, params)
    end
end


-- 常用 特例
function ListView:setFoucsIndex(index)
    local oldIndex = self:getFoucsIndex()
    if oldIndex ~= index then
        self._foucsIndex = index
        local oldCell = self:getCellByIndex(oldIndex)
        if oldCell and oldCell.onFoucsIndexChange then
            oldCell:onFoucsIndexChange(index)
        end
        
        local newCell = self:getCellByIndex(index)
        if newCell and newCell.onFoucsIndexChange then
            newCell:onFoucsIndexChange(index)
        end

        if self._foucsIndexChangeListen then
            self._foucsIndexChangeListen(index)
        end
    end
end

function ListView:getFoucsIndex()
    return self._foucsIndex or 1
end

-- 通用 cells 之间共享数据
function ListView:setCellsShareData(cellsShareData)
    self._cellsShareData = cellsShareData
    if self._shareDataChangeListen then
        self._shareDataChangeListen(self._cellsShareData)
    end
end

function ListView:getCellsShareData()
    return self._cellsShareData
end

function ListView:notifyCellsShareDataChange(index)
    if not index then
        for i = self._headIndex, self._tailIndex do
            local cellModel = self._cellModels[i]
            local cell = cellModel.cell
            if cell and cell.onCellsShareDataChange then
                cell:onCellsShareDataChange(self._cellsShareData)
            end
        end
    else
        local cell = self:getCellByIndex(index)
        if cell and cell.onCellsShareDataChange then
            cell:onCellsShareDataChange(self._cellsShareData)
        end
    end
end

return ListView
